#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "http_parser.h"
#include "liso_engine.h"
#include "cgi.h"
#include "liso_logging.h"

static int parse_status_line(char *line, client_conn_t *cliconn);
static int cgi_check(char *uri);
static int do_GET(int fd, client_conn_t *cliconn, select_engine_t *engine);
static int do_POST(int fd, client_conn_t *cliconn, select_engine_t *engine);
static int do_HEAD(int fd, client_conn_t *cliconn, select_engine_t *engine);
static int open_file(client_conn_t *cliconn, select_engine_t *engine);
static void get_mime_type(char *path, client_conn_t *cliconn);
static int send_error(client_conn_t *cliconn, char *status, char *reason);
static void build_error_message_body(char *body, char *status, char *reason);
static int add_status_line(client_conn_t *cliconn, char *status, char *reason);
static int add_to_buf(client_conn_t *cliconn, char *buf, int len);
static int build_response_header(client_conn_t *cliconn);
static int build_response_body(client_conn_t *cliconn);

int parser_http_handler(int fd, client_conn_t *cliconn, void *self)
{
	char *line;
	select_engine_t *engine = (select_engine_t *) self;
	
	if ((line = liso_buf_readline(cliconn->recvbuf)) == NULL) {
		liso_logging_log("http_parser", "parser_http_handler",
						"Send Http error message: 400 Bad Request to fd %d.\n", fd);
		return send_error(cliconn, "400", "Bad Request");
	}

	/* check status line */
	if (parse_status_line(line, cliconn)) {
		liso_logging_log("http_parser", "parser_http_handler",
						"Send Http error message: 400 Bad Request to fd %d.\n", fd);
		return send_error(cliconn, "400", "Bad Request");
	}

	/* check HTTP version */
	if (strcmp(cliconn->version, "HTTP/1.1")) {
		liso_logging_log("http_parser", "parser_http_handler",
						"Send Http error message: 505 HTTP Version not Supported to fd %d.\n", fd);
		return send_error(cliconn, "505", "HTTP Version not Supported");
	}

	if (cgi_check(cliconn->uri)) {
		if (do_CGI(fd, cliconn, engine)) {
			liso_logging_log("http_parser", "parser_http_handler",
							"Send Http error message: 500 Internal Server Error to fd %d.\n", fd);
			return send_error(cliconn, "500", "Internal Server Error");
		}
		return 1;
	}

	switch (cliconn->method) {
		case HP_MTHD_GET:
			return do_GET(fd, cliconn, engine);
		case HP_MTHD_POST:
			return do_POST(fd, cliconn, engine);
		case HP_MTHD_HEAD:
			return do_HEAD(fd, cliconn, engine);
		case HP_UNKNOWN:
			liso_logging_log("http_parser", "parser_http_handler",
							"Send Http error message: 501 Not Implemented to fd %d.\n", fd);
			return send_error(cliconn, "501", "Not Implemented");
	}

	return 0;
}


static int parse_status_line(char *line, client_conn_t *cliconn)
{
	char *ptr, *method, *uri, *version;
	
	method = line;
	if ((ptr = strchr(line, ' ')) == NULL)
		return -1;

	*ptr = '\0';
	if (strcmp(method, "GET") == 0) 
		cliconn->method = HP_MTHD_GET;
	else if (strcmp(method, "POST") == 0) 
		cliconn->method = HP_MTHD_POST;
	else if (strcmp(method, "HEAD") == 0)
		cliconn->method = HP_MTHD_HEAD;
	else 
		cliconn->method = HP_UNKNOWN;

	uri = ++ptr;
	if ((ptr = strchr(uri, ' ')) == NULL)
		return -1;

	*ptr = '\0';
	cliconn->uri = uri;
	
	version = ++ptr;
	if ((ptr = strchr(version, '\r')) == NULL)
		return -1;

	*ptr = '\0';
	cliconn->version = version;
	return 0;
}

static int cgi_check(char *uri)
{
	if (!strncmp(uri, CGI_PATH, strlen(CGI_PATH)))
		return 1;
	else
		return 0;
}

static int do_GET(int fd, client_conn_t *cliconn, select_engine_t *engine)
{
	char *line;

	/* parse header */
	while (strcmp(line, "\r")) {
		if((line = liso_buf_readline(cliconn->recvbuf)) == NULL) {
			liso_logging_log("http_parser", "do_GET",
						"Send Http error message: 400 Bad Request to fd %d.\n", fd);
			return send_error(cliconn, "400", "Bad Request");
		}
		else if (!strncasecmp(line, "CONNECTION", strlen("CONNECTION"))) {
			if (!strncasecmp(line+strlen("CONNECTION: "), "CLOSE", strlen("CLOSE"))) {
				cliconn->connclose = HP_CONN_CLOSE; 
			}
			else
				cliconn->connclose = HP_CONN_KEEPALIVE;
		}
	}
		
	if (open_file(cliconn, engine)) {
		liso_logging_log("http_parser", "do_GET",
						"Send Http error message: 404 Not Found to fd %d.\n", fd);
		return send_error(cliconn, "404", "Not Found");
	}
	
	if(build_response_header(cliconn)) {
		liso_logging_log("http_parser", "do_GET",
						"Can not build response header for fd %d.\n", fd);
		fclose(cliconn->pFile);
		return -1;
	}
		
	if (build_response_body(cliconn)) {
		liso_logging_log("http_parser", "do_GET",
						"Can not build response body for fd %d.\n", fd);
		fclose(cliconn->pFile);
		return -1;
	}
	
	fclose(cliconn->pFile);
	return 0;
}

static int do_POST(int fd, client_conn_t *cliconn, select_engine_t *engine)
{
	char *line;

	cliconn->contentlen = -1;
	/* parse header */
	while (strcmp(line, "\r")) {
		if((line = liso_buf_readline(cliconn->recvbuf)) == NULL) {
			liso_logging_log("http_parser", "do_POST",
						"Send Http error message: 400 Bad Request to fd %d.\n", fd);
			return send_error(cliconn, "400", "Bad Request");
		}
		else if (!strncasecmp(line, "CONNECTION", strlen("CONNECTION"))) {
			if (!strncasecmp(line+strlen("CONNECTION: "), "CLOSE", 
						strlen("CLOSE"))) {
				cliconn->connclose = HP_CONN_CLOSE; 
			}
			else
				cliconn->connclose = HP_CONN_KEEPALIVE;
		}
		else if (!strncasecmp(line, "Content-Length", strlen("Content-Length"))) {
			cliconn->contentlen = atoi(line+strlen("Content-Length: "));
		}
	}

	if (cliconn->contentlen == -1) {
		liso_logging_log("http_parser", "do_POST",
						"Send Http error message: 411 Length Required to fd %d.\n", fd);
		return send_error(cliconn, "411", "Length Required");
	}

	/* build headers */
	if (add_status_line(cliconn, "200", "OK")) {
		liso_logging_log("http_parser", "do_POST",
						"Can not build response header for fd %d.\n", fd);
		return -1;
	}

	if (add_to_buf(cliconn, "\r\n", strlen("\r\n"))) {
		liso_logging_log("http_parser", "do_POST",
						"Can not build response header for fd %d.\n", fd);
		return -1;
	}
	/* end headers */

	return 0;
}

static int do_HEAD(int fd, client_conn_t *cliconn, select_engine_t *engine)
{
	char *line;

	/* parse header */
	while (strcmp(line, "\r")) {
		if((line = liso_buf_readline(cliconn->recvbuf)) == NULL) {
			liso_logging_log("http_parser", "do_HEAD",
						"Send Http error message: 400 Bad Request to fd %d.\n", fd);
			return send_error(cliconn, "400", "Bad Request");
		}
		else if (!strncasecmp(line, "CONNECTION", strlen("CONNECTION"))) {
			if (!strncasecmp(line+strlen("CONNECTION: "), "CLOSE", strlen("CLOSE"))) {
				cliconn->connclose = HP_CONN_CLOSE; 
			}
			else
				cliconn->connclose = HP_CONN_KEEPALIVE;
		}
	}
		
	if (open_file(cliconn, engine)) {
		liso_logging_log("http_parser", "do_HEAD",
						"Send Http error message: 404 Not Found to fd %d.\n", fd);
		return send_error(cliconn, "404", "Not Found");
	}
	
	if(build_response_header(cliconn)) {
		liso_logging_log("http_parser", "do_HEAD",
						"Can not build response header for fd %d.\n", fd);
		fclose(cliconn->pFile);
		return -1;
	}

	return 0;
}

static int open_file(client_conn_t *cliconn, select_engine_t *engine)
{
	char path[MAX_LINE];
	/* build path */
	strcpy(path, engine->www);
	if (!strcmp(cliconn->uri, "/")) 
		strcat(path, "index.html");
	else 
		strcat(path, cliconn->uri);

	if ((cliconn->pFile = fopen(path, "r")) == NULL) {
		return -1;
	}	

	if (stat(path, &cliconn->fstat)) {
		fclose(cliconn->pFile);
		return -1;
	}

	get_mime_type(path, cliconn);
	return 0;
}

static void get_mime_type(char *path, client_conn_t *cliconn)
{
	char *suffix;
	suffix = strchr(path, '.');

	if (suffix == NULL)
		cliconn->mime_type = HP_UNKNOWN;
	else {
		suffix++;
		if (!strcmp(suffix, "html"))
			cliconn->mime_type = HP_TEXT_HTML;
		else if (!strcmp(suffix, "css"))
			cliconn->mime_type = HP_TEXT_CSS;
		else if (!strcmp(suffix, "png"))
			cliconn->mime_type = HP_IMAGE_PNG;
		else if (!strcmp(suffix, "jpeg"))
			cliconn->mime_type = HP_IMAGE_JPEG;
		else if (!strcmp(suffix, "gif"))
			cliconn->mime_type = HP_IMAGE_GIF;
		else
			cliconn->mime_type = HP_UNKNOWN;
	}
}

static int send_error(client_conn_t *cliconn, char *status, char *reason)
{
	char buf[BUF_SIZE], body[BUF_SIZE];
	time_t rawtime;
	
	build_error_message_body(body, status, reason);
	/* add status line */
	if (add_status_line(cliconn, status, reason))
		return -1;

	rawtime = time(NULL);
	strftime(buf, sizeof(buf), "Date: %a, %d %b %Y %H:%M:%S GMT\r\n",
			gmtime(&rawtime));
	
	sprintf(buf, "%sServer: Liso\r\n", buf);
	sprintf(buf, "%sContent-Type: text/html\r\n", buf);
	sprintf(buf, "%sContent-Length: %d\r\n", buf, (int)strlen(body));
	sprintf(buf, "%s\r\n", buf);
	sprintf(buf, "%s%s", buf, body);

	/* add error message body */
	return add_to_buf(cliconn, buf, strlen(buf));
}

static void build_error_message_body(char *body, char *status, char *reason)
{
	sprintf(body, "<html><title>Liso Error</title>");
	sprintf(body, "%s<body bgcolor=""ffffff"">\r\n", body);
	sprintf(body, "%s%s: %s\r\n", body, status, reason);
	sprintf(body, "%s<hr><em>The Liso Web server</em>\r\n", body);
	sprintf(body, "%s</body></html>", body);
}

static int add_to_buf(client_conn_t *cliconn, char *buf, int len)
{
	/* if sendbuf == NULL, allocate new buffer */
	if (cliconn->sendbuf == NULL) {
		if ((cliconn->sendbuf = make_new_buf()) == NULL)
			return -1;
	}
	
	/* if there is no enough space in sendbuf, extend sendbuf */
	if ((cliconn->sendbuf->bufsize - cliconn->sendbuf->buflen) 
			< len) {
		if(extend_liso_buf(cliconn->sendbuf))
			return -1;
	}
	
	memcpy(cliconn->sendbuf->bufptr, buf, len);
	cliconn->sendbuf->buflen += len;
	cliconn->sendbuf->bufptr += len;
	return 0;
}

static int add_status_line(client_conn_t *cliconn, char *status, char *reason)
{
	char buf[MAX_LINE];
	sprintf(buf, "HTTP/1.1 %s %s\r\n", status, reason);
	
	return add_to_buf(cliconn, buf, strlen(buf));
}

static int build_response_header(client_conn_t *cliconn)
{
	char buf[BUF_SIZE], mtime[100];
	time_t rawtime;
	/* add status line */
	if(add_status_line(cliconn, "200", "OK"))
		return -1;

	rawtime = time(NULL);
	strftime(buf, sizeof(buf), "Date: %a, %d %b %Y %H:%M:%S GMT\r\n",
			gmtime(&rawtime));
	
	sprintf(buf, "%sServer: Liso\r\n", buf);
	
	if (cliconn->connclose) 
		sprintf(buf, "%sConnection: close\r\n", buf);
	else
		sprintf(buf, "%sConnection: keep-alive\r\n", buf);

	sprintf(buf, "%sContent-Length: %lld\r\n", 
			buf, (long long)cliconn->fstat.st_size);
	
	switch (cliconn->mime_type) {
		case HP_TEXT_HTML:
			sprintf(buf, "%sContent-Type: text/html\r\n", buf);
			break;
		case HP_TEXT_CSS:
			sprintf(buf, "%sContent-Type: text/css\r\n", buf);
			break;
		case HP_IMAGE_PNG:
			sprintf(buf, "%sContent-Type: image/png\r\n", buf);
			break;
		case HP_IMAGE_JPEG:
			sprintf(buf, "%sContent-Type: image/jpeg\r\n", buf);
			break;
		case HP_IMAGE_GIF:
			sprintf(buf, "%sContent-Type: image/gif\r\n", buf);
			break;
	}

	strftime(mtime, sizeof(mtime), "%a, %d %b %Y %H:%M:%S GMT",
			 gmtime(&cliconn->fstat.st_mtime));
	sprintf(buf, "%sLast-Modified: %s\r\n", buf, mtime);
	sprintf(buf, "%s\r\n", buf);

	return add_to_buf(cliconn, buf, strlen(buf));
}

static int build_response_body(client_conn_t *cliconn)
{
	long long remain;
	char buf[BUF_SIZE];

	remain = cliconn->fstat.st_size;
	while (remain) {
		if (remain < BUF_SIZE) {
			fread(buf, sizeof(char), remain, cliconn->pFile);
			if (add_to_buf(cliconn, buf, remain))
				return -1;
			remain -= remain;
		}
		else {
			fread(buf, sizeof(char), BUF_SIZE, cliconn->pFile);
			if (add_to_buf(cliconn, buf, BUF_SIZE))
				return -1;
			remain -= BUF_SIZE;
		}
	}

	return 0;
}
